package com.vanke.agent.common;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.log4j.Logger;

import com.vanke.agent.ADataSource;

public final class GUIDCacheChecker {
	public static void main(String[] args) {
		if (args.length == 0) {
			usage();
			return;
		}
		if ("rebuild".equals(args[0])) {
			rebuildReceiveTables();
		} else if ("treceive".equals(args[0])) {
			if (args.length < 2) {
				usage();
				return;
			}
			int check = existsMyGUID(args[1]);
			System.out.println("check result: " + check);
		} else {
			if (args.length < 2) {
				return;
			}
			int etype = -1;
			try {
				etype = Integer.parseInt(args[1]);
			} catch (Exception e) {
				etype = -1;
			}
			if (etype == -1) {
				return;
			}
			int check = existsGUID(args[0], null, etype);
			System.out.println("check result: " + check);
		}
	} //~

	private static void usage() {
		System.out.println("usage:");
		System.out
				.println("java -cp .:$CLASSPATH com.vanke.agent.common.GUIDCacheChecker option");
		System.out.println("option values:");
		System.out
				.println("\trebuild  --load all MyGUID and ReceiveDate from tables cst_FollowRecord_XXX, and save to guid_cstreceive_X");
		System.out
				.println("\tdayonce  --run test every day run at [04:00, 06:00)");
		System.out
				.println("\treceive [GUID] --check cstreceive myguid exists or not");
		System.out.println("\tGUID etype --check origuid exists or not");
		System.out.println("\t\t0 customer");
		System.out.println("\t\t1 order");
		System.out.println("\t\t2 contract");
	}

	static void rebuildReceiveTables() {
		Connection bizCon = ADataSource.getConnection(ADataSource.SCHEMA_BIZ);
		Statement stmt = null;
		ResultSet rs = null;
		try {
			int count = 0;
			stmt = bizCon.createStatement();
			rs = stmt.executeQuery("show tables");
			while (rs.next()) {
				String table = rs.getString(1);
				if (table.toLowerCase().startsWith("cst_followrecord_")) {
					count += rebuildReceive(bizCon, table);
				}
			}
			log.info("all over, total " + count + " myguid rebuild");
		} catch (Exception e) {
			log.error("rebuild receive table error:" + e);
		} finally {
			ADataSource.close(rs, stmt, bizCon);
		}
	}

	static int rebuildReceive(Connection bizCon, String table) {
		Statement stmt = null;
		ResultSet rs = null;
		int count = 0;

		Map<String, Map<String, Timestamp>> tblData = new HashMap<String, Map<String, Timestamp>>();
		try {
			String sql = "select MyGUID,ReceiveDate from "
					+ table
					+ " where MyGUID is not null and ReceiveDate>='2014-09-01 00:00:00'";
			stmt = bizCon.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
					ResultSet.CONCUR_READ_ONLY);
			rs = stmt.executeQuery(sql);
			log.info("execute:" + sql);
			while (rs.next()) {
				count++;
				Timestamp t = rs.getTimestamp(2);
				String myguid = rs.getString(1);
				String suffix = myguid.substring(myguid.length() - 1);
				Map<String, Timestamp> map = tblData.get(suffix);
				if (map == null) {
					map = new HashMap<String, Timestamp>();
					tblData.put(suffix, map);
				}
				map.put(myguid, t);
				if (count % 1000 == 0) {
					log.info(count + " record readed");
				}
			} // end while
			log.info("table over, total " + count + " myguid rebuild from "
					+ table);
			Iterator<Entry<String, Map<String, Timestamp>>> dIter;
			dIter = tblData.entrySet().iterator();
			int dcount = 0;
			while (dIter.hasNext()) {
				Entry<String, Map<String, Timestamp>> dEntry = dIter.next();
				dcount += saveRebuildData(bizCon, dEntry.getValue(),
						dEntry.getKey());
			} // end while(dIter.hasNext())
			log.info("table over, total " + dcount + " myguid saved");
		} catch (Exception e) {
			log.error("load " + table + " cache error:" + e);
		} finally {
			ADataSource.close(rs, stmt, null);
		}
		log.info("total " + count + " myguid rebuild from " + table);
		return count;
	} //~

	static int saveRebuildData(Connection bizCon, Map<String, Timestamp> data,
			String suffix) throws SQLException {
		String tbl = retrieveCstReceiveTable(suffix);
		PreparedStatement pstmt = bizCon.prepareStatement("insert into " + tbl
				+ "(OriGUID,Modify,CreateTime)values(?,?,?)");
		int count = 0;
		Iterator<Entry<String, Timestamp>> iter;
		iter = data.entrySet().iterator();
		while (iter.hasNext()) {
			count++;
			Entry<String, Timestamp> entry = iter.next();
			pstmt.setString(1, entry.getKey());
			pstmt.setTimestamp(2, entry.getValue());
			pstmt.setTimestamp(3, new Timestamp(System.currentTimeMillis()));
			pstmt.addBatch();
			if (count % 5000 == 0) {
				pstmt.executeBatch();
				pstmt.clearBatch();
				log.info("total " + count + " myguid save to " + tbl);
			}
		}
		pstmt.executeBatch();
		pstmt.clearBatch();
		log.info("total " + count + " myguid save to " + tbl);
		return count;
	}

	static void testDayOnceExec() {
		new Thread(new Runnable() {

			@Override
			public void run() {
				int rebackOffset = 1;
				int lastDay = Calendar.getInstance().get(Calendar.DAY_OF_MONTH);
				while (true) {
					rebackOffset = 1;
					Calendar cal = Calendar.getInstance();
					int hour = cal.get(Calendar.HOUR_OF_DAY);
					if (hour == 4 || hour == 5) {
						int day = cal.get(Calendar.DAY_OF_MONTH);
						if (day != lastDay) {
							rebackOffset = 2;
							lastDay = day;
						}
					}
					System.out.println(rebackOffset == 1 ? "normal"
							: "day once run");
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}).start();

	}

	static Logger log = Logger.getLogger(GUIDCacheChecker.class);
	static Set<String> cstReceiveTables = new HashSet<String>();
	static {
		Connection con = ADataSource.getConnection(ADataSource.SCHEMA_BIZ);
		Statement stmt = null;
		ResultSet rs = null;
		try {
			boolean cst = false, order = false, contract = false;
			stmt = con.createStatement();
			rs = stmt.executeQuery("show tables");
			while (rs.next()) {
				String table = rs.getString(1).toLowerCase();
				if (table.equals("guid_customer")) {
					cst = true;
				}
				if (table.equals("guid_order")) {
					order = true;
				}
				if (table.equals("guid_contract")) {
					contract = true;
				}
				if (table.startsWith("guid_cstreceive")) {
					cstReceiveTables.add(table);
				}
			} // end while
			if (!cst) {
				stmt.executeUpdate("create table guid_customer("
						+ "OriGUID varchar(64) not null," //
						+ "Modify datetime,"              //
						+ "CreateTime datetime not null," //
						+ "primary key(OriGUID)"          //
						+ ")DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci");
			}
			if (!order) {
				stmt.executeUpdate("create table guid_order("
						+ "OriGUID varchar(64) not null," //
						+ "Modify datetime,"              //
						+ "CreateTime datetime not null," //
						+ "primary key(OriGUID)"          //
						+ ")DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci");
			}
			if (!contract) {
				stmt.executeUpdate("create table guid_contract("
						+ "OriGUID varchar(64) not null," //
						+ "Modify datetime,"              //
						+ "CreateTime datetime not null," //
						+ "primary key(OriGUID)"          //
						+ ")DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci");
			}
		} catch (Exception e) {
			e.printStackTrace();
			log.error("check guid cache table error:" + e);
		} finally {
			ADataSource.close(rs, stmt, con);
		}
	}
	protected static Map<String, Map<String, Long>> cstGuidMap = new HashMap<String, Map<String, Long>>();
	protected static Map<String, Map<String, Long>> cstOrderMap = new HashMap<String, Map<String, Long>>();
	protected static Map<String, Map<String, Long>> cstContractMap = new HashMap<String, Map<String, Long>>();

	public static final int ETYPE_CST = 0;
	public static final int ETYPE_ORDER = 1;
	public static final int ETYPE_CONTRACT = 2;

	// 表示ＧＵＩＤ不存在
	public static final int CHECK_NOT_EXIST = 0;

	// 表示ＧＵＩＤ已存在，且修改时间一样
	public static final int CHECK_SAME = 1;

	// 表示ＧＵＩＤ已存在，但修改时间不一样
	public static final int CHECK_EXIST = 2;

	public static void loadGuidCache(int etype) {
		CEntity ce = getCEntity(etype);
		if (ce == null) {
			log.info("loadGuidCache(): unknown etype[" + etype + "]");
			return;
		}
		Connection bizCon = ADataSource.getConnection(ADataSource.SCHEMA_BIZ);
		Statement stmt = null;
		ResultSet rs = null;
		int count = 0;
		try {
			String sql = "select OriGUID,Modify from " + ce.table;
			stmt = bizCon.createStatement();
			rs = stmt.executeQuery(sql);
			log.info("execute:" + sql);
			while (rs.next()) {
				count++;
				Timestamp t = rs.getTimestamp(2);
				cacheGuidMapping(rs.getString(1), t.getTime(), ce.map);
			}
		} catch (Exception e) {
			log.error("load " + ce.table + " cache error:" + e);
		} finally {
			ADataSource.close(rs, stmt, bizCon);
		}
		log.info("total " + count + " myguid cached from " + ce.table);
	}

	public static void saveGUID(String guid, Timestamp modify, int etype) {
		if (guid == null || guid.length() < 2) {
			log.info("updateGUID(): invalid guid[" + guid + "]");
			return;
		}
		CEntity ce = getCEntity(etype);
		if (ce == null) {
			log.info("saveGUID(): unknown etype[" + etype + "]");
			return;
		}
		long modifyMillis = modify == null ? -1 : modify.getTime();
		cacheGuidMapping(guid, modifyMillis, ce.map);

		Connection bizCon = ADataSource.getConnection(ADataSource.SCHEMA_BIZ);
		PreparedStatement pstmt = null;
		try {
			String sql = "insert into " + ce.table
					+ "(OriGUID,Modify,CreateTime)values(?,?,?)";
			log.info("execute:" + sql + "[" + Util.formatDate(modify) + "]");
			pstmt = bizCon.prepareStatement(sql);
			pstmt.setString(1, guid);
			pstmt.setTimestamp(2, modify);
			pstmt.setTimestamp(3, new Timestamp(System.currentTimeMillis()));
			pstmt.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
			log.error("save " + ce.table + " cache error:" + e);
		} finally {
			ADataSource.close(null, pstmt, bizCon);
		}
	} //~

	public static void updateGUID(String guid, Timestamp modify, int etype) {
		if (guid == null || guid.length() < 2) {
			log.info("updateGUID(): invalid guid[" + guid + "]");
			return;
		}
		CEntity ce = getCEntity(etype);
		if (ce == null) {
			log.info("updateGUID(): unknown etype[" + etype + "]");
			return;
		}
		long modifyMillis = modify == null ? -1 : modify.getTime();
		cacheGuidMapping(guid, modifyMillis, ce.map);

		Connection bizCon = ADataSource.getConnection(ADataSource.SCHEMA_BIZ);
		PreparedStatement pstmt = null;
		try {
			String sql = "update " + ce.table + " set Modify=? where OriGUID=?";
			log.info("execute:" + sql + "[" + Util.formatDate(modify) + "]");
			pstmt = bizCon.prepareStatement(sql);
			pstmt.setTimestamp(1, modify);
			pstmt.setString(2, guid);
			pstmt.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
			log.error("update " + ce.table + " cache error:" + e);
		} finally {
			ADataSource.close(null, pstmt, bizCon);
		}
	} //~

	public static int existsGUID(String guid, Timestamp modify, int etype) {
		int check = CHECK_NOT_EXIST;
		if (guid == null || guid.length() < 2) {
			log.info("existsGUID(): invalid guid[" + guid + "]");
			return check;
		}
		CEntity ce = getCEntity(etype);
		if (ce == null) {
			log.info("existsGUID(): unknown etype[" + etype + "]");
			return check;
		}
		// 查内存
		long modifyMilli = modify == null ? -1 : modify.getTime() / 1000;
		long time = get(guid, ce.map);
		if (time != 0) {
			return time / 1000 == modifyMilli ? CHECK_SAME : CHECK_EXIST;
		}

		// 内存查不到查数据库
		Connection bizCon = ADataSource.getConnection(ADataSource.SCHEMA_BIZ);
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try {
			String sql = "select OriGUID,Modify from " + ce.table
					+ " where OriGUID=?";
			//			log.info("execute:" + sql + "[" + Util.formatDate(modify) + "]");
			pstmt = bizCon.prepareStatement(sql);
			pstmt.setString(1, guid);
			rs = pstmt.executeQuery();
			Timestamp t = null;
			if (rs.next()) {
				check = CHECK_EXIST;
				t = rs.getTimestamp(2);
			}
			long tMillis = t == null ? 0 : t.getTime();
			if (tMillis == modifyMilli) {
				check = CHECK_SAME;
			}
		} catch (Exception e) {
			e.printStackTrace();
			log.error("check " + ce.table + " cache error:" + e);
		} finally {
			ADataSource.close(rs, pstmt, bizCon);
		}
		if (check != CHECK_NOT_EXIST) {
			// 数据库查到了，则缓存到内存Ｆ
			cacheGuidMapping(guid, modifyMilli, ce.map);
		}
		return check;
	} //~

	public static void release(int etype) {
		CEntity ce = getCEntity(etype);
		if (ce != null) {
			ce.map.clear();
		}
	}

	private static void cacheGuidMapping(String guid, long time,
			Map<String, Map<String, Long>> cacheMap) {
		String suffix = guid.substring(guid.length() - 2);
		Map<String, Long> map = cacheMap.get(suffix);
		if (map == null) {
			map = new HashMap<String, Long>();
			cacheMap.put(suffix, map);
		}
		map.put(guid, time);
	}

	private static long get(String key, Map<String, Map<String, Long>> cacheMap) {
		try {
			String suffix = key.substring(key.length() - 2);
			Map<String, Long> map = cacheMap.get(suffix);
			Long guid = map == null ? new Long(0) : map.get(key);
			return guid == null ? 0 : guid;
		} catch (Exception e) {
			e.printStackTrace();
			return 0;
		}
	}

	private static CEntity getCEntity(int etype) {
		CEntity ce = new CEntity();
		switch (etype) {
		case ETYPE_CST:
			ce.map = cstGuidMap;
			ce.table = "guid_customer";
			break;
		case ETYPE_ORDER:
			ce.map = cstOrderMap;
			ce.table = "guid_order";
			break;
		case ETYPE_CONTRACT:
			ce.map = cstContractMap;
			ce.table = "guid_contract";
			break;
		default:
			return null;
		}
		return ce;
	}

	private static class CEntity {
		// 赋初值，先保证没有空指针异常
		Map<String, Map<String, Long>> map = new HashMap<String, Map<String, Long>>();
		String table = "";
	}

	//-------------------------------------------------------------------------
	protected static Map<String, Map<String, Long>> cstReceiveMap = new HashMap<String, Map<String, Long>>();
	static String cstReceiveGuidTable = "guid_cstreceive";

	public static void saveMYGUID(String myguid, Timestamp modify) {
		if (myguid == null || myguid.length() < 2) {
			log.info("saveMYGUID(): invalid myguid[" + myguid + "]");
			return;
		}
		long modifyMillis = modify == null ? -1 : modify.getTime();
		cacheGuidMapping(myguid, modifyMillis, cstReceiveMap);

		String table = retrieveCstReceiveTable(myguid);
		Connection bizCon = ADataSource.getConnection(ADataSource.SCHEMA_BIZ);
		PreparedStatement pstmt = null;
		try {
			String sql = "insert into " + table
					+ "(OriGUID,Modify,CreateTime)values(?,?,?)";
			//			log.info("execute:" + sql + "[" + Util.formatDate(modify) + "]");
			pstmt = bizCon.prepareStatement(sql);
			pstmt.setString(1, myguid);
			pstmt.setTimestamp(2, modify);
			pstmt.setTimestamp(3, new Timestamp(System.currentTimeMillis()));
			pstmt.executeUpdate();
		} catch (Exception e) {
			e.printStackTrace();
			log.error("save " + table + " cache error:" + e);
		} finally {
			ADataSource.close(null, pstmt, bizCon);
		}
	} //~saveMYGUID(String myguid, Timestamp modify)

	public static int existsMyGUID(String myguid) {
		int check = CHECK_NOT_EXIST;
		if (myguid == null || myguid.length() < 2) {
			log.info("existsMyGUID(): invalid myguid[" + myguid + "]");
			return check;
		}
		// 查内存
		long time = get(myguid, cstReceiveMap);
		if (time != 0) {
			return CHECK_SAME;
		}

		// 内存查不到查数据库
		String table = retrieveCstReceiveTable(myguid);
		Connection bizCon = ADataSource.getConnection(ADataSource.SCHEMA_BIZ);
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		try {
			String sql = "select OriGUID from " + table + " where OriGUID=?";
			pstmt = bizCon.prepareStatement(sql);
			pstmt.setString(1, myguid);
			rs = pstmt.executeQuery();
			if (rs.next()) {
				check = CHECK_SAME;
			}
		} catch (Exception e) {
			e.printStackTrace();
			log.error("check receive " + table + " cache error:" + e);
		} finally {
			ADataSource.close(rs, pstmt, bizCon);
		}
		if (check != CHECK_NOT_EXIST) {
			// 数据库查到了，则缓存到内存Ｆ
			cacheGuidMapping(myguid, -1, cstReceiveMap);
		}
		return check;
	}

	private static String retrieveCstReceiveTable(String myguid) {
		String table = cstReceiveGuidTable;
		if (myguid != null & myguid.length() > 0) {
			table = cstReceiveGuidTable + "_"
					+ myguid.substring(myguid.length() - 1);
		}
		if (cstReceiveGuidTable.contains(table)) {
			return table;
		}
		Connection con = ADataSource.getConnection(ADataSource.SCHEMA_BIZ);
		Statement stmt = null;
		ResultSet rs = null;
		try {
			boolean found = false;
			stmt = con.createStatement();
			rs = stmt.executeQuery("show tables");
			while (rs.next()) {
				String t = rs.getString(1);
				if (table.equals(t)) {
					found = true;
					break;
				}
			} // end while
			if (!found) {
				stmt.executeUpdate("create table " + table
						+ "(OriGUID varchar(64) not null," //
						+ "Modify datetime,"              //
						+ "CreateTime datetime not null," //
						+ "primary key(OriGUID)"          //
						+ ")DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci");
			}
			cstReceiveTables.add(table);
		} catch (Exception e) {
			e.printStackTrace();
			log.error("check cstreceive guid cache table error:" + e);
		} finally {
			ADataSource.close(rs, stmt, con);
		}
		return table;
	} //~retrieveCstReceiveTable

	public static void loadReceiveGuidCache() {
		Connection bizCon = ADataSource.getConnection(ADataSource.SCHEMA_BIZ);
		Statement stmt = null;
		ResultSet rs = null;
		try {
			int count = 0;
			log.info("加载myguid映射缓存，请稍候...");
			stmt = bizCon.createStatement();
			rs = stmt.executeQuery("show tables");
			while (rs.next()) {
				String table = rs.getString(1);
				if (table.startsWith(cstReceiveGuidTable)) {
					count += loadReceiveGuidCache(bizCon, table);
				}
			}
			log.info("共计 " + count + " myguid 已缓存");
		} catch (Exception e) {
			log.error("load receive cache error:" + e);
		} finally {
			ADataSource.close(rs, stmt, bizCon);
		}
	}

	private static int loadReceiveGuidCache(Connection bizCon, String table) {
		Statement stmt = null;
		ResultSet rs = null;
		int count = 0;
		try {
			String sql = "select OriGUID,Modify from " + table;
			stmt = bizCon.createStatement();
			rs = stmt.executeQuery(sql);
			//			log.info("execute:" + sql);
			while (rs.next()) {
				count++;
				Timestamp t = rs.getTimestamp(2);
				cacheGuidMapping(rs.getString(1), t == null ? -2 : t.getTime(),
						cstReceiveMap);
			}
		} catch (Exception e) {
			log.error("load " + table + " cache error:" + e);
		} finally {
			ADataSource.close(rs, stmt, null);
		}
		return count;
	}

	public static void releaseReceive() {
		cstReceiveMap.clear();
	}
} //~
